#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#include "config.h"
#include "parser.h"
#include "scanner.h"
#include "symbol.h"
#include "list.h"
#include "debug.h"
#include "reg.h"

extern FILE *output_fp;
extern char *output_file_name;
extern char *input_file_name;

int auto_size = 0;
int current_level = 0;
int current_block = 0;
int local_seq = 0;
int local_total_offset = 0;

extern struct cminus_token t;
struct cminus_symbol *current_function;
int current_section;

static int first_time_error = 1;

static void declaration();
static void func_declaration(struct cminus_token type_token,
                             struct cminus_token id_token);
static void var_declaration(struct cminus_token type_token,
                            struct cminus_token id_token);
static void params();
static void param_list();
static struct list_head * param();
static void compound_stmt();
static struct list_head *local_var_declaration();
static void statement_list(void);
static void statement();
static void assign_stmt();
static void selection_stmt();
static void iteration_stmt();
static void return_stmt();
static void write_stmt();
static void expression();
static void additive_expression();
static void term();
static void factor();
static int var(void);
static void call();
static void args();

static void match(int type);
static void syntax_error(const char *fmt, ... );
static void common_syntax_error();
static int  get_label();
static void skip_until(int n, ...);

void output(const char *fmt, ...)
{
  va_list ap;
  /*output asm code*/
  va_start(ap, fmt);
  vfprintf(output_fp, fmt, ap);
  va_end(ap);
}

/**
 * program->{declaration}
 */
void program()
{
  time_t timep;
  time (&timep);
  /**
   * write assemble file header
   */
  output(";c source file::%s\n", input_file_name);
  output(";asm dest file:%s\n", output_file_name);
  output(";time:%s", ctime(&timep));
  output(";cminus compiler version:%s\n\n", CMINUS_VERSION);

  output("extern printf\n");
  output("extern scanf\n");
  output("section .data\n");
  output("%s:\n", WRITE_MSGE);
  output("db \"%%d\",10,0\n");
  output("%s:\n", READ_MSGE);
  output("db \"%%d\",0\n");
  output("section .bss\n");
  output("%s resb 4\n", INPUT_VALUE);
  current_section = BSS_SECTION;

  /**
   * parse each declaration
   * first(declaration)={VOID INT}
   */
  while (INT == t.type || VOID == t.type)
  {
    declaration();
  }

  /**
   * write assemble file end
   */
  output("\n;asm file end\n");
}

/**
 * declaration->var_declaration | func_declaration
 */
static void declaration()
{
  struct cminus_token type_token;
  struct cminus_token id_token;

  /*read ahead to recongnize if it is a func decl or a var decl*/
  type_token = t;
  id_token = get_token();

  t = get_token();
  if (LPARAN == t.type)/*func declaration*/
  {
    func_declaration(type_token, id_token);
  }
  else if (SEMI == t.type || LSQUARE == t.type)/*var declaration*/
  {
    var_declaration(type_token, id_token);
  }
  else
  {
    common_syntax_error();
    skip_until(2, VOID, INT);
  }
}


/**
 * syntax:func_declaration->type_specifier ID LPARAN params RPARAN compound_stmt
 * semantic:
 */
static void func_declaration(struct cminus_token type_token,
                             struct cminus_token id_token)
{
  unsigned long last_pos = 0;
  unsigned long cur_pos = 0;

  auto_size = 0;
  current_level = 0;
  current_block = 0;
  local_seq = 0;
  local_total_offset = 0;

  clear_reg_bit_set();

  /*match '('*/
  match(LPARAN);

  /*insert function name in symbol table*/
  current_function = insert_function_symbol(id_token.name);
  if (NULL == current_function)
  {
    __ERROR_MSG("error when insert function symbol:%s\n", id_token.name);
  }

  current_function->u.f.return_type = type_token.type;

  /*get the param list*/
  params();

  /*match ')'*/
  match(RPARAN);
  /*generate function header*/
  if (current_section != TEXT_SECTION)
  {
    output("\nsection .text\n");
    current_section = TEXT_SECTION;
  }

  output(";definition of function %s\n", id_token.name);
  output("global %s\n", id_token.name);
  output("%s:\n", id_token.name);
  output("push ebp;\n");
  output("mov ebp, esp;\n");

  output("sub esp, 000;\n");
  last_pos = ftell(output_fp);
  last_pos -= strlen("000;\n");

  compound_stmt();

  /*write auto size back*/
  cur_pos = ftell(output_fp);
  fseek(output_fp, last_pos, SEEK_SET);
  output("%3d", auto_size);
  auto_size = 0;
  current_level = 0;
  current_block = 0;

  /*restore r/w point*/
  fseek(output_fp, cur_pos, SEEK_SET);

  output("leave;\n");
  if (current_function->u.f.param_size)
  {
    output("ret %d;\n\n", current_function->u.f.param_size);
  }
  else
  {
    output("ret;\n\n");
  }

  return;
} 

/**
 * syntax:var_declaration->type_specifier ID [ LSQUARE NUM RSQUARE ];
 * semantic:
 */
static void var_declaration(struct cminus_token type_token,
                            struct cminus_token id_token)
{
  struct cminus_symbol *sym = NULL;
  int result = 0;

  if (type_token.type != INT)
  {
    semantic_error("the var type can't be %s\n", type_token.name);
    return;
  }

  sym = malloc(sizeof(struct cminus_symbol));
  if (NULL == sym)
  {
    __ERROR_MSG("error when malloc\n");
    exit(-1);
  }

  memset((void *)sym, 0, sizeof(struct cminus_symbol));

  INIT_LIST_HEAD(&sym->sym_list);

  sym->sym_type = GLOBAL_VAR;
  strncpy(sym->name, id_token.name, MAX_NAME_SZ);
  sym->u.g.type = type_token.type;

  if (current_section != BSS_SECTION)
  {
    output("\nsection .bss\n");
    current_section = BSS_SECTION;
  }

  if (SEMI == t.type)
  {
    sym->u.g.size = sizeof(int);
    sym->u.g.is_array = 0;
    match(SEMI);
  }
  else
  {
    match(LSQUARE);
    sym->u.g.size = sizeof(int) * atoi(t.name);
    sym->u.g.is_array = 1;
    match(NUM);
    match(RSQUARE);
    match(SEMI);
  }

  output("%s resb %d\n", id_token.name, sym->u.g.size);

  result = insert_global_var_symbol(sym);
  if (-1 == result)
  {
    __ERROR_MSG("error when insert global var symbol\n");
  }

  return;
}

/**
 * syntax:params->VOID | param_list
 * semantic:return the param list
 */
static void params()
{
  if (VOID == t.type)
  {
    match(VOID);
  }
  else if (INT == t.type)
  {
    param_list();
  }
  else
  {
    common_syntax_error();
    skip_until(1, RPARAN);
  }

  return;
}

/**
 * syntax:param_list->param{COMMA param}
 * semantic:return the param list
 */
static void param_list()
{
  struct list_head *temp = NULL;
  struct param_info *_param = NULL;
  int seq = 0;

  temp = param();
  _param = list_entry(temp, struct param_info, param_list);
  _param->seq = seq;
  _param->offset = seq * sizeof(int);
  seq ++;
  current_function->u.f.param_size += _param->size;
  list_add_tail(temp, &current_function->u.f.param_head);

  while (COMMA == t.type)
  {
    match(COMMA);
    temp = param();
    _param = list_entry(temp, struct param_info, param_list);
    _param->seq = seq;
    _param->offset = seq * sizeof(int);
    seq ++;

    current_function->u.f.param_size += _param->size;
    list_add_tail(temp, &current_function->u.f.param_head);
  }

  return;
}

/**
 * syntax:param->type_specifier ID [LSQUARE RSQUARE]
 * semantic:return an entry for param
 */
static struct list_head *param()
{
  struct param_info *param = NULL;

  if (t.type != INT)
  {
    semantic_error("the param type can't be %s\n", t.name);
  }

  param = malloc(sizeof(struct param_info));
  if (NULL == param)
  {
    __ERROR_MSG("error when malloc\n");
  }
  memset((void *)param, 0, sizeof(struct param_info));
  INIT_LIST_HEAD(&param->param_list);

  param->type = t.type;
  match(INT);
  strncpy(param->name, t.name, MAX_NAME_SZ);
  param->size = sizeof(int);

  match(ID);

  if (LSQUARE == t.type)
  {
    param->is_array = 1;
    match(LSQUARE);
    match(RSQUARE);
  }
  else
  {
    param->is_array = 0;
  }

  return &param->param_list;
}

/**
 * syntax:compound_stmt->LBRACE {local_var_declaration} statement_list RBRACE
 * semantic:
 */
static void compound_stmt()
{
  struct list_head *temp;
  struct auto_info *auto_var = NULL;

  match(LBRACE);

  while (INT == t.type)
  {
    temp = local_var_declaration();
    auto_var = list_entry(temp, struct auto_info, auto_list);
    auto_var->seq = local_seq;
    auto_var->level = current_level;
    auto_var->block_no = current_block;
    auto_var->offset = local_total_offset;
    local_total_offset += auto_var->size;
    local_seq ++;
    auto_size += auto_var->size;
    list_add_tail(temp, &current_function->u.f.auto_head);
  }

  current_level ++;
  current_block ++;

  statement_list();
  match(RBRACE);

  current_level --;
}

/**
 * syntax:local_var_declaration->type_specifier ID [ LSQUARE NUM RSQUARE ];
 * semantic:return auto symbol information
 */
static struct list_head *local_var_declaration()
{
  struct auto_info *_auto_info = NULL;

  if (t.type != INT)
  {
    semantic_error("the var type can't be %s\n", t.name);
  }

  _auto_info = malloc(sizeof(struct auto_info));
  if (NULL == _auto_info)
  {
    __ERROR_MSG("error when malloc\n");
    exit(-1);
  }
  memset((void *)_auto_info, 0, sizeof(struct auto_info));

  INIT_LIST_HEAD(&_auto_info->auto_list);
  _auto_info->type = t.type;
  match(INT);
  strncpy(_auto_info->name, t.name, MAX_NAME_SZ);

  if (LSQUARE == t.type)
  {
    match(LSQUARE);
    _auto_info->size = atoi(t.name) * sizeof(int);
    _auto_info->is_array = 1;

    match(NUM);
    match(RSQUARE);
  }
  else
  {
    _auto_info->size = sizeof(int);
    _auto_info->is_array = 0;
  }

  match(ID);

  match(SEMI);

  return &_auto_info->auto_list;
}

/**
 * syntax:statement_list->{statement}
 * semantic:
 */
static void statement_list(void)
{
  /**
  * first(statement) = {LBRACE ID RETURN IF WHILE WRITE}
  */
  while (IF == t.type || LBRACE == t.type
         || ID == t.type || WHILE == t.type
         || RETURN == t.type || WRITE == t.type)
  {
    statement();
  }
}

/**
 * syntax:statement->assign_stmt | compound_stmt | selection_stmt
 * | iteration_stmt | return_stmt | write_stmt
 * semantic:
 */
static void statement()
{
  struct cminus_token second_token;
  int pos = 0;

  if (ID == t.type)
  {
    pos = virtual_get_token(1);
    second_token = token_buffer[pos];

    if (LPARAN == second_token.type)/*it's a call factor*/
    {
      factor();
      match(SEMI);
    }
    else
    {
      assign_stmt();
    }
  }
  else if (LBRACE == t.type)
  {
    /**
     * first(compound_stmt) = {LBRACE}
     */
    compound_stmt();
  }
  else if (IF == t.type)
  {
    /**
     * first(selection_stmt) = {IF}
     */
    selection_stmt();
  }
  else if (WHILE == t.type)
  {
    /**
     * first(iteration_stmt) = {WHILE}
     */
    iteration_stmt();
  }
  else if (RETURN == t.type)
  {
    /**
     * first(return_stmt) = {RETURN}
     */
    return_stmt();
  }
  else if (WRITE == t.type)
  {
    /**
     * first(write_stmt) = {WRITE}
     */
    write_stmt();
  }
  else
  {
    common_syntax_error();
    /**
     * follow(statement) = {ID ELSE SEMI LBRACE NUM RBRACE IF RETURN LPARAN WHILE}
     */
    skip_until(10, ID, ELSE, SEMI, NUM, LBRACE,
               RETURN, IF, RBRACE, LPARAN, WHILE);
  }

  return;
}

/**
 * syntax:selection_stmt->IF LPARAN expression RPARAN statement [ELSE statement]
 * semantic:
 */
static void selection_stmt()
{
  int first_label = -1;
  int second_label = -1;

  match(IF);
  match(LPARAN);

  output(";start selection statement\n");

  expression();
  output("cmp %s, 0;\n", reg_name[REG_EXPR_RET]);

  first_label = get_label();
  output("jz near L%d;\n", first_label);

  match(RPARAN);

  statement();

  second_label = get_label();
  output("jmp near L%d;\n", second_label);
  output("L%d:\n", first_label);

  if (ELSE == t.type)
  {
    match(ELSE);
    statement();
  }

  output("L%d:\n", second_label);
  output(";end selection statement\n");
}

/**
 * syntax:iteration_stmt->while LPARAN expression RPARAN statement
 * semantic:
 */
static void iteration_stmt()
{
  int first_label = -1;
  int second_label = -1;

  output(";start iteration_stmt\n");

  match(WHILE);
  match(LPARAN);

  first_label = get_label();
  output("L%d:\n", first_label);

  expression();
  output("cmp %s, 0;\n", reg_name[REG_EXPR_RET]);

  second_label = get_label();
  output("jz near L%d;\n", second_label);

  match(RPARAN);
  statement();
  output("jmp near L%d;\n", first_label);
  output("L%d:\n", second_label);

  output(";end iteration_stmt\n");
}

/**
 * syntax:return_stmt->RETURN SEMI | RETURN expression SEMI
 * semantic:
 */
static void return_stmt()
{
  output(";start return stmt\n");
  match(RETURN);

  if (SEMI == t.type)
  {
    match(SEMI);

    if (current_function->u.f.return_type != VOID)
    {
      semantic_error("return wrong type\n");
    }

    return;
  }

  expression();

  if (current_function->u.f.return_type == VOID)
  {
    semantic_error("return wrong type\n");
  }

  match(SEMI);

  output(";end return stmt\n");

  return;
}

/**
 * syntax:write_stmt->WRITE LPARAN expression RPARAN SEMI
 * semantic:call printf to output expression
 */
static void write_stmt()
{
  int i = 0;

  match(WRITE);
  match(LPARAN);

  expression();

  /*save common registers*/
  for (i = 0; i < COMMON_REG_NUM; i++)
  {
    output("push %s;\n", reg_name[i]);
  }

  output("push %s;\n", reg_name[REG_EXPR_RET]);
  output("push dword %s;\n", WRITE_MSGE);
  output("call printf;\n");

  output("add esp, 8;\n");/*clean param space*/

  /*restore common registers*/
  for (i = 0; i < COMMON_REG_NUM; i++)
  {
    output("pop %s;\n", reg_name[COMMON_REG_NUM - i - 1]);
  }

  match(RPARAN);
  match(SEMI);
}

/**
 * syntax:assign_stmt->var ASSIGN expression SEMI
 * semantic:
 */
static void assign_stmt()
{
  int var_addr_reg = -1;
  int result = 0;

  var();
  var_addr_reg = get_reg();

  output("mov %s, %s;\n", reg_name[var_addr_reg], reg_name[REG_EXPR_RET]);

  match(ASSIGN);

  expression();

  output("mov [%s], %s;\n", reg_name[var_addr_reg], reg_name[REG_EXPR_RET]);

  result = free_reg(var_addr_reg);
  if (-1 == result)
  {
    __ERROR_MSG("error when free var addr reg:%s\n", reg_name[var_addr_reg]);
  }

  match(SEMI);
}

/**
 * syntax:expression->additive_expression {realop additive_expression}
 *        expression->READ LPARAN RPARAN
 * semantic:return the expression value, the value is in REG_EXPR_RET
 */
static void expression()
{
  int last_reg = -1;
  struct cminus_token op_token;
  int label = -1;
  int label_bottom = -1;
  int result = -1;
  int i = 0;

  if (READ == t.type)
  {
    match(READ);
    match(LPARAN);
    match(RPARAN);

    /*save common registers*/
    for (i = 0; i < COMMON_REG_NUM; i++)
    {
      output("push %s;\n", reg_name[i]);
    }

    output("push %s;\n", INPUT_VALUE);
    output("push dword %s;\n", READ_MSGE);
    output("call scanf;\n");

    output("add esp, 8;\n");/*clean param space*/

    /*restore common registers*/
    for (i = 0; i < COMMON_REG_NUM; i++)
    {
      output("pop %s;\n", reg_name[COMMON_REG_NUM - i - 1]);
    }

    output("mov %s, [%s];\n", reg_name[REG_EXPR_RET], INPUT_VALUE);

  }
  else
  {

    additive_expression();

    if (!(EQ == t.type || NEQ == t.type
          || LT == t.type || GT == t.type
          || NGT == t.type || NLT == t.type))
    {
      return;
    }

    last_reg = get_reg();
    output(";start expression\n");
    output("mov %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);

    /**
     * first(realop) = {EQ NGT NEQ NLT LT GT}
     */
    while (EQ == t.type || NEQ == t.type
           || LT == t.type || GT == t.type
           || NGT == t.type || NLT == t.type)
    {
      op_token = t;
      match(t.type);
      additive_expression();

      output("cmp %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);

      label = get_label();

      if (EQ == op_token.type)/*==*/
      {
        output("je near L%d;\n", label);
      }
      else if (NEQ == op_token.type)/*!=*/
      {
        output("jne near L%d;\n", label);
      }
      else if (GT == op_token.type)/*>*/
      {
        output("jg near L%d;\n", label);
      }
      else if (LT == op_token.type)/*<*/
      {
        output("jl near L%d;\n", label);
      }
      else if (NGT == op_token.type)/*<=*/
      {
        output("jle near L%d;\n", label);
      }
      else if (NLT == op_token.type)/*>=*/
      {
        output("jge near L%d;\n", label);
      }

      label_bottom = get_label();
      output("mov %s, 0;\n", reg_name[last_reg]);
      output("jmp near L%d;\n", label_bottom);
      output("L%d:\n", label);
      output("mov %s, 1;\n", reg_name[last_reg]);
      output("L%d:\n", label_bottom);
    }

    output("mov %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[last_reg]);
    output(";end expression\n");

    result = free_reg(last_reg);
    if (-1 == result)
    {
      __ERROR_MSG("error when free last reg:%s\n", reg_name[last_reg]);
    }
  }

  return;
}

/**
 * syntax:additive_expression->term {addop term}
 * semantic:
 */
static void additive_expression()
{
  int last_reg = -1;
  int result = -1;
  struct cminus_token op_token;

  term();

  if (!(PLUS == t.type || MINUS == t.type))
  {
    return;
  }

  last_reg = get_reg();
  output(";start additive_expression\n");
  output("mov %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);

  /**
   * first(addop) = {MINUS PLUS}
   */
  while (PLUS == t.type || MINUS == t.type)
  {
    op_token = t;
    match(t.type);
    term();

    if (PLUS == op_token.type)/*+*/
    {
      output("add %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);
    }
    else if (MINUS == op_token.type)/*-*/
    {
      output("sub %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);
    }
    else
    {
      common_syntax_error();
      /**
       * follow(additive_expression) = {EQ RSQUARE NEQ COMMA SEMI LT RPARAN NGT NLT GT}
       */
      skip_until(10, EQ, RSQUARE, NEQ, COMMA, SEMI, LT, RPARAN, NGT, NLT, GT);
    }
  }

  output("mov %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[last_reg]);
  output(";end additive_expression\n");

  result = free_reg(last_reg);
  if (-1 == result)
  {
    __ERROR_MSG("error when free last reg:%s\n", reg_name[last_reg]);
  }

  return;
}

/**
 * syntax:term->factor {mulop factor}
 * semantic:
 */
static void term()
{
  int last_reg = -1;
  int result = -1;
  struct cminus_token op_token;

  factor();

  if (!(TIMES == t.type || DIV == t.type))
  {
    return;
  }

  last_reg = get_div_reg();
  output(";start term\n");
  output("mov %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);

  /**
   * first(mulop) = {TIMES DIV}
   */
  while (TIMES == t.type || DIV == t.type)
  {
    op_token = t;
    match(t.type);
    factor();

    if (TIMES == op_token.type)/*'*'*/
    {
      output("imul %s, %s;\n", reg_name[last_reg], reg_name[REG_EXPR_RET]);
    }
    else if (DIV == op_token.type)/*'/'*/
    {
      result = get_specified_reg(EAX);
      if (-1 == result)
      {
        __ERROR_MSG("error when get specified reg:eax\n");
      }

      result = get_specified_reg(EDX);
      if (-1 == result)
      {
        __ERROR_MSG("error when get specified reg:edx\n");
      }

      output("mov %s, 0;\n", reg_name[EDX]);
      output("mov %s, %s;\n", reg_name[EAX], reg_name[last_reg]);
      output("idiv %s;\n", reg_name[REG_EXPR_RET]);
      output("mov %s, %s;\n", reg_name[last_reg], reg_name[EAX]);

      result = free_reg(EDX);
      if (-1 == result)
      {
        __ERROR_MSG("error when free specified reg:%s\n", reg_name[EDX]);
      }

      result = free_reg(EAX);
      if (-1 == result)
      {
        __ERROR_MSG("error when free specified reg:%s\n", reg_name[EAX]);
      }
    }
    else
    {
      common_syntax_error();
      /**
       * follow(term) = {EQ RSQUARE MINUS NEQ LT SEMI COMMA PLUS RPARAN NGT NLT GT}
       */
      skip_until(12, EQ, RSQUARE, MINUS, NEQ, LT, SEMI,
                 COMMA, PLUS, RPARAN, NGT, NLT, GT);
    }
  }

  output("mov %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[last_reg]);
  output(";end term\n");

  result = free_reg(last_reg);
  if (-1 == result)
  {
    __ERROR_MSG("error when free last reg:%s\n", reg_name[last_reg]);
  }

  return;
}

/**
 * syntax:factor->LPARAN expression RPARAN | var | call | NUM
 * semantic:
 */
static void factor()
{
  int var_addr_reg = -1;
  int pos = 0;
  int result = -1;
  struct cminus_token second_token;

  if (LPARAN == t.type)
  {
    match(LPARAN);
    expression();
    match(RPARAN);
    return;
  }
  /**
   * first(var) = {ID}
   * first(call) = {ID}
   * so we need to read ahead one word
   */
  else if (ID == t.type)
  {
    pos = virtual_get_token(1);
    second_token = token_buffer[pos];
    if (LPARAN == second_token.type)/*call*/
    {
      call();
      return;
    }
    else
    {
      result = var();

      if (!result)/*not the start of an array*/
      {
        var_addr_reg = get_reg();
        output("mov %s, %s;\n", reg_name[var_addr_reg], reg_name[REG_EXPR_RET]);
        output("mov %s, [%s];\n", reg_name[REG_EXPR_RET], reg_name[var_addr_reg]);
        result = free_reg(var_addr_reg);
        if (-1 == result)
        {
          __ERROR_MSG("error when free var addr reg:%s\n", reg_name[var_addr_reg]);
        }

        return;
      }
      else/*the expression's value is an address,so do nothing*/
      {
        return;
      }
    }
  }
  else if (NUM == t.type)
  {
    output("mov %s, %s;\n", reg_name[REG_EXPR_RET], t.name);
    match(NUM);
    return;
  }
  else
  {
    common_syntax_error();
    /**
     * follow(factor) = 
     * {EQ RSQUARE MINUS TIMES NEQ DIV PLUS COMMA SEMI LT RPARAN NGT NLT GT}
     */
    skip_until(14, EQ, RSQUARE, MINUS, TIMES, NEQ, DIV, PLUS,
               COMMA, SEMI, LT, RPARAN, NGT, NLT, GT);
  }

  return;
}

/**
 * syntax:var->ID [LSQUARE expression RSQUARE]
 * semantic:return the reg which contains the var's address
 * and tell caller if this var is the start of an array
 */
static int var(void)
{
  int temp_reg = -1;
  int result = -1;
  struct cminus_symbol *sym = NULL;
  struct auto_info *_auto = NULL;
  struct param_info *_param = NULL;
  char sym_name[MAX_NAME_SZ] = {0};

  strncpy(sym_name, t.name, MAX_NAME_SZ);

  sym = lookup_global_var(t.name);
  _auto = lookup_auto_var(t.name);
  _param = lookup_param(t.name);

  match(ID);

  if (_auto && _param)
  {
    semantic_error("param and auto var can't have the same name\n");
  }

  if (LSQUARE == t.type)/*a member of array*/
  {
    /**
     * TODO:add semantic check here to check if var is a array
     */
    match(LSQUARE);
    expression();


    match(RSQUARE);

    if (_param)
    {
      temp_reg = get_reg();
      if (-1 == temp_reg)
      {
        __ERROR_MSG("error when get_reg\n");
      }

      output(";start get address of a member of param array\n");
      output("mov %s ,[%s+%d];\n", reg_name[temp_reg], reg_name[EBP],
             8 + current_function->u.f.param_size - _param->offset - _param->size);
      output("imul %s, 4;\n", reg_name[REG_EXPR_RET]);
      output("add %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[temp_reg]);
      output(";end get address of a member of param array\n");

      result = free_reg(temp_reg);
      if (-1 == result)
      {
        __ERROR_MSG("error when free temp reg:%s\n", reg_name[temp_reg]);
      }
    }
    else if (_auto)
    {
      temp_reg = get_reg();
      if (-1 == temp_reg)
      {
        __ERROR_MSG("error when get_reg\n");
      }

      output(";start get address of a member of auto array\n");
      output("mov %s, %s;\n", reg_name[temp_reg], reg_name[EBP]);
      output("sub %s, %d;\n", reg_name[temp_reg], _auto->offset + _auto->size);
      output("imul %s, 4;\n", reg_name[REG_EXPR_RET]);
      output("add %s, %s;\n",reg_name[REG_EXPR_RET], reg_name[temp_reg]);
      output(";end get address of a member of auto array\n");

      result = free_reg(temp_reg);
      if (-1 == result)
      {
        __ERROR_MSG("error when free temp reg:%s\n", reg_name[temp_reg]);
      }
    }
    else if (sym)
    {
      output(";start get address of a member of global array\n");
      output("imul %s, 4;\n", reg_name[REG_EXPR_RET]);
      output("add %s, %s;\n",reg_name[REG_EXPR_RET], sym->name);
      output(";end get address of a member of global array\n");
    }
    else
    {
      semantic_error("can't find symbol:%s\n", sym_name);
    }

  }
  else/*not member of an array*/
  {
    if (_param)
    {
      temp_reg = get_reg();
      if (-1 == temp_reg)
      {
        __ERROR_MSG("error when get_reg\n");
      }

      output(";start get address of a parameter:%s\n", sym_name);
      output("mov %s, %s;\n", reg_name[temp_reg], reg_name[EBP]);
      output("add %s, %d;\n",  reg_name[temp_reg],
             8 + current_function->u.f.param_size - _param->offset - _param->size);
      output("mov %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[temp_reg]);
      output(";end get address of a parameter:%s\n", sym_name);

      result = free_reg(temp_reg);
      if (-1 == result)
      {
        __ERROR_MSG("error when free temp reg:%s\n", reg_name[temp_reg]);
      }
    }
    else if (_auto)
    {
      temp_reg = get_reg();
      if (-1 == temp_reg)
      {
        __ERROR_MSG("error when get_reg\n");
      }

      output(";start get address of a auto variable:%s\n", sym_name);
      output("mov %s, %s;\n", reg_name[temp_reg], reg_name[EBP]);
      output("sub %s, %d;\n", reg_name[temp_reg], _auto->offset + 4);
      output("mov %s, %s;\n", reg_name[REG_EXPR_RET], reg_name[temp_reg]);
      output(";end get address of a auto variable:%s\n", sym_name);

      result = free_reg(temp_reg);
      if (-1 == result)
      {
        __ERROR_MSG("error when free temp reg:%s\n", reg_name[temp_reg]);
      }

      if (_auto->is_array)
      {
        return 1;
      }
    }
    else if (sym)
    {
      output(";start get address of a global variable:%s\n", sym_name);
      output("mov %s, %s;\n", reg_name[REG_EXPR_RET], sym->name);
      output(";end get address of a global variable:%s\n", sym_name);
      if (sym->u.g.is_array)
      {
        return 1;
      }
    }
    else
    {
      semantic_error("can't find symbol:%s\n", sym_name);
      return -1;
    }
  }

  return 0;
}

/**
 * syntax:call->ID LPARAN args RPARAN
 */
static void call()
{
  struct cminus_token id;
  int i = 0;

  id = t;

  match(ID);
  match(LPARAN);
  output(";start to call %s\n", id.name);

  /*save common registers*/
  for (i = 0; i < COMMON_REG_NUM; i++)
  {
    output("push %s;\n", reg_name[i]);
  }

  args();

  output("call %s;\n", id.name);

  /*restore common registers*/
  for (i = 0; i < COMMON_REG_NUM; i++)
  {
    output("pop %s;\n", reg_name[COMMON_REG_NUM - i - 1]);
  }

  output(";end call %s\n", id.name);

  match(RPARAN);
}

/**
 * syntax:args->EMPTY | expression{COMMA expression}
 * semantic:
 */
static void args()
{
  if (RPARAN == t.type)
  {
    return;
  }

  expression();
  output("push %s;\n", reg_name[REG_EXPR_RET]);

  while (COMMA == t.type)
  {
    match(COMMA);
    expression();
    output("push %s;\n", reg_name[REG_EXPR_RET]);
  }
}

/**
 * utility function
 */

static void match(int type)
{
  if (t.type == type)
  {
    t = get_token();
  }
  else
  {
    syntax_error("expect character %d\n", type);
  }
}

static void syntax_error(const char *fmt, ...)
{
  char command[64] = {0};
  va_list ap;

  if (first_time_error)/**/
  {
    /*redirect output_fp*/
    fclose(output_fp);
    output_fp = fopen("/dev/null", "r+");
    first_time_error = 0;
    /*rm asm file*/
    sprintf(command, "rm -f %s", output_file_name);
    system(command);
  }

  /*print error header*/
  fprintf(stderr, "%s:%d: syntax error: ", input_file_name, line_no);

  /*print error message*/
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);

  return;
}

static void common_syntax_error()
{
  char command[64] = {0};

  int line_no = 0;

  if (first_time_error)/**/
  {
    /*redirect output_fp*/
    fclose(output_fp);
    output_fp = fopen("/dev/null", "r+");
    first_time_error = 0;
    /*rm asm file*/
    sprintf(command, "rm -f %s", output_file_name);
    system(command);
  }

  /*print error message*/
  fprintf(stderr, "%s:%d: syntax error: syntax error near %s\n",
          input_file_name, line_no, t.name);

  return;
}

void semantic_error(const char *fmt, ...)
{
  char command[64] = {0};
  va_list ap;

  if (first_time_error)/**/
  {
    /*redirect output_fp*/
    fclose(output_fp);
    output_fp = fopen("/dev/null", "r+");
    first_time_error = 0;
    /*rm asm file*/
    sprintf(command, "rm -f %s", output_file_name);
    system(command);
  }

  /*print error header*/
  fprintf(stderr, "%s:%d: semantic error: ", input_file_name, line_no);
  /*print error message*/
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);

  return;
}

static int get_label()
{
  static int label = 0;
  return label++;
}

static void skip_until(int n, ...)
{
  int arg_num = n;
  int token_type = -1;
  va_list ap;
  int flag = 1;

  while (flag)
  {
    va_start(ap, n);

    while (arg_num > 0)
    {
      token_type = va_arg(ap, int);

      if (t.type == token_type)
      {
        flag = 0;
        break;
      }

      arg_num--;
    }

    va_end(ap);

    t = get_token();
  }

  return;
}



